#ifndef SQLITE_HPP
#define SQLITE_HPP

#include <sqlite3.h>
#include <string>
#include <functional>
#include <memory>
#include <sstream>


class SQLiteStatement
{
public:
	SQLiteStatement(sqlite3_stmt *stmt) :
		stmt_(stmt)
	{
	}

	~SQLiteStatement()
	{
		if (stmt_)
			sqlite3_finalize(stmt_);
	}

	bool step(std::string &error)
	{
		int err = sqlite3_step(stmt_);
		if (err != SQLITE_ROW && err != SQLITE_DONE && err != SQLITE_OK)
		{
			error = sqlite3_errstr(err);
			return false;
		}
		return true;
	}

	bool reset(std::string &error)
	{
		int err = sqlite3_reset(stmt_);
		if (err != 0)
			err = sqlite3_clear_bindings(stmt_);
		if (err)
		{
			error = sqlite3_errstr(err);
			return false;
		}
		return true;
	}

	bool bind(int index, const char *value, std::string &error)
	{
		int err = sqlite3_bind_text(stmt_, index, value, -1, SQLITE_TRANSIENT);
		if (err)
		{
			error = sqlite3_errstr(err);
			return false;
		}
		return true;
	}

	bool bind(int index, int value, std::string &error)
	{
		int err = sqlite3_bind_int(stmt_, index, value);
		if (err)
		{
			error = sqlite3_errstr(err);
			return false;
		}
		return true;
	}

	bool bind(int index, double value, std::string &error)
	{
		int err = sqlite3_bind_double(stmt_, index, value);
		if (err)
		{
			error = sqlite3_errstr(err);
			return false;
		}
		return true;
	}

	bool bindNull(int index, std::string &error)
	{
		int err = sqlite3_bind_null(stmt_, index);
		if (err)
		{
			error = sqlite3_errstr(err);
			return false;
		}
		return true;
	}

private:
	sqlite3_stmt *stmt_;
};


class SQLiteDB
{
public:
	typedef std::function<void(int argc, char *argv[], char **colNames)> Callback;

	SQLiteDB() : db_(nullptr) { }
	~SQLiteDB() { close(); }

	bool open(const char *filename, std::string &error)
	{
		int err = sqlite3_open(filename, &db_);
		if (err)
		{
			auto msg = sqlite3_errstr(err);
			std::ostringstream ss;
			ss << "Nao foi possivel abrir o arquivo \"" << filename << "\": " << msg;
			error = ss.str();

			return false;
		}

		return true;
	}

	void close()
	{
		if (db_)
			sqlite3_close(db_);
	}

	bool exec(const char *sql, std::string &error, Callback cb = nullptr)
	{
		char *msg = nullptr;

		cb_ = cb;
		int err = sqlite3_exec(db_, sql, &SQLiteDB::exec_callback, this, &msg);
		cb_ = nullptr;

		if (err)
		{
			error = msg;
			sqlite3_free(msg);
			return false;
		}

		return true;
	}

	std::shared_ptr<SQLiteStatement> createStatement(const char *sql, std::string &error)
	{
		sqlite3_stmt *stmt = nullptr;
		int err = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
		if (err)
		{
			error = sqlite3_errstr(err);
			return nullptr;
		}

		return std::make_shared<SQLiteStatement>(stmt);
	}

	sqlite3_int64 lastInsertRowId() const { return sqlite3_last_insert_rowid(db_); }

private:
	static int exec_callback(void *ctx, int argc, char *argv[], char **colNames)
	{
		auto &obj = *reinterpret_cast<SQLiteDB *>(ctx);
		if (obj.cb_)
			obj.cb_(argc, argv, colNames);
		return 0;
	}

	Callback cb_;
	sqlite3 *db_;
};


#endif
